package com.unitd.scheduler.job;

import com.unitd.frame.spring.SpringInstanceFactory;
import com.unitd.frame.spring.SpringInstanceProvider;
import com.unitd.frame.spring.helper.SpringAopHelper;
import com.unitd.scheduler.annotation.ScheduleConf;
import com.unitd.scheduler.handler.IConfigPersistHandler;
import com.unitd.scheduler.handler.IJobLogPersistHandler;
import com.unitd.scheduler.registry.IJobRegistry;
import com.unitd.frame.comm.utils.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.quartz.Trigger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.Ordered;
import org.springframework.core.PriorityOrdered;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.scheduling.quartz.CronTriggerFactoryBean;
import org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.util.ClassUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * @desc 调度器的Bean工厂包装类
 * @filename SchedulerFactoryBeanWrapper.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2016/10/15
 */
public class SchedulerFactoryBeanWrapper implements ApplicationContextAware, InitializingBean, DisposableBean, PriorityOrdered {

	protected static final Logger logger = LoggerFactory.getLogger(SchedulerFactoryBeanWrapper.class);

	private final String RESOURCE_PATTERN = "/**/*.class";

	/** Spring的当前上下文对象 */
	private ApplicationContext context;
	/** 定时器的分组 */
	private String groupName;
	/** 定时任务集合 */
	List<AbstractJob> schedulers = new ArrayList<>();
	/** 需要扫描的包路径(扫描该包下是否有ScheduleConf注解的类,有该注解的类也是定时任务) */
	private String scanPackages;
	/** 线程池大小 */
	private int threadPoolSize;

	public void setGroupName(String groupName) {
		this.groupName = groupName;
		JobContext.getContext().setGroupName(groupName);
	}

	public void setSchedulers(List<AbstractJob> schedulers) {
		this.schedulers = schedulers;
	}

	public void setScanPackages(String scanPackages) {
		this.scanPackages = scanPackages;
	}

	public void setThreadPoolSize(int threadPoolSize) {
		this.threadPoolSize = threadPoolSize;
	}

	public void setConfigPersistHandler(IConfigPersistHandler configPersistHandler) {
		JobContext.getContext().setConfigPersistHandler(configPersistHandler);
	}

	public void setRegistry(IJobRegistry registry) {
		JobContext.getContext().setRegistry(registry);
	}

	public void setJobLogPersistHandler(IJobLogPersistHandler jobLogPersistHandler) {
		JobContext.getContext().setJobLogPersistHandler(jobLogPersistHandler);
	}

	/**
	 * @desc 实现ApplicationContextAware接口, 重写设置Spring上下文对象
	 * @param applicationContext Spring上下文对象
	 */
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.context = applicationContext;
		SpringInstanceFactory.setSpringInstanceProvider(new SpringInstanceProvider(context));
	}

	/**
	 * @desc 实现InitializingBean接口, 重写读取properties属性文件后的处理方法
	 */
	@Override
	public void afterPropertiesSet() throws Exception {
		Validate.notBlank(groupName);
		DefaultListableBeanFactory acf = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();

		if (StringUtils.isNotBlank(scanPackages)) {
			String[] packages = org.springframework.util.StringUtils.tokenizeToStringArray(this.scanPackages, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
			scanAndRegisterAnnotationJobs(packages);
		}

		List<Trigger> triggers = new ArrayList<>();
		for (AbstractJob sch : schedulers) {
			sch.setGroup(groupName);
			sch.init();
			triggers.add(registerSchedulerTriggerBean(acf, sch));
		}

		String beanName = "schedulerFactory";
		BeanDefinitionBuilder beanDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(SchedulerFactoryBean.class);
		beanDefBuilder.addPropertyValue("triggers", triggers);

		Properties quartzProperties = new Properties();

		threadPoolSize = threadPoolSize > 0 ? threadPoolSize : (schedulers.size() > 10 ? (schedulers.size() / 2 + 1) : schedulers.size());
		quartzProperties.setProperty(SchedulerFactoryBean.PROP_THREAD_COUNT, String.valueOf(threadPoolSize));
		beanDefBuilder.addPropertyValue("quartzProperties", quartzProperties);
		logger.info("init Scheduler threadPoolSize:" + threadPoolSize);

		acf.registerBeanDefinition(beanName, beanDefBuilder.getRawBeanDefinition());

		for (AbstractJob sch : schedulers) {

			// 通过AOP方式,获取目标Job并加入job上下文对象中
			final AbstractJob job = (AbstractJob) SpringAopHelper.getTarget(sch);
			JobContext.getContext().addJob(job);

			// 将获取的job加入有异步执行队列,等待线程执行
			JobContext.getContext().submitSyncTask(new Runnable() {
				@Override
				public void run() {
					// 阻塞,直到spring初始化完成
					SpringInstanceFactory.waitUtilInitialized();
					job.afterInitialized();

					if (job.isExecuteOnStarted()) {
						logger.info("<<Job[{}] execute on startup....", job.jobName);
						job.execute();
						logger.info(">>Job[{}] execute on startup ok!", job.jobName);
					}
				}
			});
			logger.info(">>>>>>> Job[{}][{}]-Class[{}]  initialized finish ", job.group, job.jobName, job.getClass().getName());
		}

		JobContext.getContext().getRegistry().onRegistered();
	}

	/**
	 * @desc 继承Spring的DisposableBean类的destroy方法,每次调用完成后,都会去自动调用此方法
	 */
	@Override
	public void destroy() throws Exception {
		JobContext.getContext().close();
	}

	/**
	 * @desc 设置执行顺序
	 * @return int
	 */
	@Override
	public int getOrder() {
		return Ordered.LOWEST_PRECEDENCE;
	}

	/**
	 * @desc 注册定时任务调度器对象(将spring_job.xml文件中定义的Job读取,并将定时任务详情获取并注册到调度工厂,方便工厂去调用)
	 * @param acf Spring的Bean工厂对西昂
	 * @param sch 定时任务对象
	 * @return Trigger
	 */
	private Trigger registerSchedulerTriggerBean(DefaultListableBeanFactory acf, AbstractJob sch) {
		// 注册JobDetail
		String jobDetailBeanName = sch.getJobName() + "JobDetail";
		if (context.containsBean(jobDetailBeanName)) {
			throw new RuntimeException("duplicate jobName[" + sch.getJobName() + "] defined!!");
		}
		BeanDefinitionBuilder beanDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(MethodInvokingJobDetailFactoryBean.class);
		beanDefBuilder.addPropertyValue("targetObject", sch);
		beanDefBuilder.addPropertyValue("targetMethod", "execute");
		beanDefBuilder.addPropertyValue("concurrent", false);
		acf.registerBeanDefinition(jobDetailBeanName, beanDefBuilder.getRawBeanDefinition());

		// 注册Trigger
		String triggerBeanName = sch.getJobName() + "Trigger";
		beanDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(CronTriggerFactoryBean.class);
		beanDefBuilder.addPropertyReference("jobDetail", jobDetailBeanName);
		beanDefBuilder.addPropertyValue("cronExpression", sch.getCronExpr());
		beanDefBuilder.addPropertyValue("group", groupName);
		acf.registerBeanDefinition(triggerBeanName, beanDefBuilder.getRawBeanDefinition());

		return (Trigger) context.getBean(triggerBeanName);
	}

	/**
	 * @desc 扫描代码包,并根据注解初始化定时器对象,并将定时任务详情获取并注册到调度工厂,方便工厂去调用
	 * 当前方法与registerSchedulerTriggerBean方法功能相同,只是一个时通过读取spring的配置文件来注册和初始化定时器任务对象,一个时通过注解来实现
	 * @param scanBasePackages 需要扫描的代码包
	 */
	private void scanAndRegisterAnnotationJobs(String[] scanBasePackages) {

		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		for (String scanBasePackage : scanBasePackages) {
			logger.info(">>begin scan package [{}] with Annotation[ScheduleConf] jobs ", scanBasePackage);
			try {
				String pattern = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX + ClassUtils.convertClassNameToResourcePath(scanBasePackage)
						+ RESOURCE_PATTERN;
				org.springframework.core.io.Resource[] resources = resourcePatternResolver.getResources(pattern);
				MetadataReaderFactory readerFactory = new CachingMetadataReaderFactory(resourcePatternResolver);
				for (org.springframework.core.io.Resource resource : resources) {
					if (resource.isReadable()) {
						MetadataReader reader = readerFactory.getMetadataReader(resource);
						String className = reader.getClassMetadata().getClassName();
						Class<?> clazz = Class.forName(className);
						if (clazz.isAnnotationPresent(ScheduleConf.class)) {
							ScheduleConf annotation = clazz.getAnnotation(ScheduleConf.class);
							AbstractJob job = (AbstractJob) context.getBean(clazz);
							job.setCronExpr(annotation.cronExpr());
							job.setExecuteOnStarted(annotation.executeOnStarted());
							job.setGroup(groupName);
							job.setJobName(annotation.jobName());
							job.setRetries(annotation.retries());
							if (!schedulers.contains(job)) {
								schedulers.add(job);
								logger.info("register new job:{}", ToStringBuilder.reflectionToString(job, ToStringStyle.JSON_STYLE));
							} else {
								logger.info("Job[{}] is registered", job.getJobName());
							}
						}
					}
				}
				logger.info("<<scan package[" + scanBasePackage + "] finished!");
			} catch (Exception e) {
				if (e instanceof org.springframework.beans.factory.NoSuchBeanDefinitionException) {
					throw (org.springframework.beans.factory.NoSuchBeanDefinitionException) e;
				}
				logger.error("<<scan package[" + scanBasePackage + "] error", e);
			}
		}
	}
}